using System;
using System.Collections.Generic;
using System.Text;
using LibGreen.Jabber.Connection;
using LibGreen.Jabber.ProtocolElements;
using System.Globalization;

namespace LibGreen.Jabber.Managers
{
    /// <summary>
    /// Describes a contact status change event
    /// </summary>
    public class ContactStatusChangeEventArgs : EventArgs
    {
        private readonly Jid contact;
        private readonly Presence currentStatus;
        private readonly string primaryStatusMessage;

        /// <summary>
        /// Initializes a new ContactStatusChangeEventArgs
        /// </summary>
        /// <param name="contact">The <see cref="Jid"/> of the contact whose status has changed</param>
        /// <param name="currentStatus">The contact's current <see cref="Presence"/> setting</param>
        /// <param name="primaryStatusMessage">The contact's primary status message, determined
        /// by the <see cref="PresenceManager.PreferredLanguage"/> property</param>
        public ContactStatusChangeEventArgs(Jid contact, Presence currentStatus, string primaryStatusMessage)
        {
            this.contact = contact;
            this.currentStatus = currentStatus;
            this.primaryStatusMessage = primaryStatusMessage;
        }

        /// <summary>
        /// Gets the <see cref="Jid"/> of the contact whose status has changed
        /// </summary>
        public Jid Contact
        {
            get { return contact; }
        }

        /// <summary>
        /// Gets the contact's current <see cref="Presence"/> setting
        /// </summary>
        public Presence CurrentStatus
        {
            get { return currentStatus; }
        }

        /// <summary>
        /// Gets the contact's primary status message
        /// </summary>
        /// <remarks>"Primary" is determined by the <see cref="PresenceManager.PreferredLanguage"/> property</remarks>
        public string StatusMessage
        {
            get { return primaryStatusMessage; }
        }
    }

    /// <summary>
    /// Describes a subscription request event
    /// </summary>
    public class SubscriptionRequestEventArgs : EventArgs
    {
        private readonly Jid resource;

        /// <summary>
        /// Initializes a new SubscriptionRequestEventArgs
        /// </summary>
        /// <param name="resource">The <see cref="Jid"/> of the resource that has requested subscription</param>
        public SubscriptionRequestEventArgs(Jid resource)
        {
            this.resource = resource;
        }

        /// <summary>
        /// Gets the <see cref="Jid"/> of the resource that has requested subscription
        /// </summary>
        public Jid Resource
        {
            get { return resource; }
        }
    }

    /// <summary>
    /// A handler for contact status change events
    /// </summary>
    /// <param name="sender">The <see cref="PresenceManager"/> raising the event</param>
    /// <param name="e">A <see cref="ContactStatusChangeEventArgs"/> describing the event</param>
    public delegate void ContactStatusChangeEventHandler(object sender, ContactStatusChangeEventArgs e);

    /// <summary>
    /// A handler for subscription request events
    /// </summary>
    /// <param name="sender">The <see cref="PresenceManager"/> raising the event</param>
    /// <param name="e">A <see cref="SubscriptionRequestEventArgs"/> describing the event</param>
    public delegate void SubscriptionRequestEventHandler(object sender, SubscriptionRequestEventArgs e);

    /// <summary>
    /// Controls setting the presence information for the session, retrieving other contact's presence information,
    /// and presence subscription management
    /// </summary>
    public class PresenceManager
    {
        /// <summary>
        /// Raised when a contact's status changes
        /// </summary>
        public event ContactStatusChangeEventHandler ContactStatusChange;
        /// <summary>
        /// Raised when a resource requests subscription from this client
        /// </summary>
        public event SubscriptionRequestEventHandler SubscriptionRequested;

        private readonly Session parentSession;
        private Presence currentPresence = Presence.Unavailable;
        private string currentStatusMessage = "";
        private string currentStatusMessageLanguage = "";
        private CultureInfo preferredLanguage = CultureInfo.CurrentUICulture;
        private string preferredLanguageCode = CultureInfo.CurrentUICulture.TwoLetterISOLanguageName;

        /// <summary>
        /// Initializes a new PresenceManager
        /// </summary>
        /// <param name="parentSession">The <see cref="Session"/> that owns this manager</param>
        public PresenceManager(Session parentSession)
        {
            this.parentSession = parentSession;
        }

        #region Presence management methods
        /// <summary>
        /// Gets or sets the client's presence on the server
        /// </summary>
        /// <remarks>If the session is offline, the presence will be set when a new connection is established</remarks>
        public Presence CurrentPresence
        {
            get { return currentPresence; }
            set
            {
                if (value != currentPresence)
                {
                    currentPresence = value;
                    SetCurrentStatus();
                }
            }
        }

        /// <summary>
        /// Sets the client's status message on the server using the current preferred language
        /// </summary>
        /// <param name="statusMessage">The message to set</param>
        public void SetStatusMessage(string statusMessage)
        {
            SetStatusMessage(statusMessage, preferredLanguage);
            SetCurrentStatus();
        }

        /// <summary>
        /// Sets the client's status message on the server, marking it with the specified language
        /// </summary>
        /// <param name="statusMessage">The message to set</param>
        /// <param name="messageLanguage">The <see cref="CultureInfo"/> of the message's language</param>
        public void SetStatusMessage(string statusMessage, CultureInfo messageLanguage)
        {
            currentStatusMessage = statusMessage;
            currentStatusMessageLanguage = messageLanguage.TwoLetterISOLanguageName;
            SetCurrentStatus();
        }

        /// <summary>
        /// Clears the current status message
        /// </summary>
        public void ClearStatusMessage()
        {
            currentStatusMessage = "";
            currentStatusMessageLanguage = "";
            SetCurrentStatus();
        }
        #endregion

        #region Subscription management methods
        /// <summary>
        /// Send a request to a resource for update subscription
        /// </summary>
        /// <param name="resource">The resource to which to subscribe</param>
        public void SubscribeToResource(Jid resource)
        {
            if (OkayToSend)
            {
                parentSession.ServerConnection.SendXmppElement(PresenceElement.Create(resource, PresenceElementType.Subscribe));
            }
        }

        /// <summary>
        /// Allow a resource that has requested this client's updates to receive them
        /// </summary>
        /// <param name="resource">The resource that requested the subscription, received via the <see cref="SubscriptionRequested"/> event</param>
        public void ApproveSubscriptionRequest(Jid resource)
        {
            if (OkayToSend)
            {
                parentSession.ServerConnection.SendXmppElement(PresenceElement.Create(resource, PresenceElementType.Subscribed));
            }
        }

        /// <summary>
        /// Prevent a resource that has requested this client's update from receiving them
        /// </summary>
        /// <param name="resource">The resource that requested the subscription, received via the <see cref="SubscriptionRequested"/> event</param>
        public void DenySubscriptionRequest(Jid resource)
        {
            if (OkayToSend)
            {
                parentSession.ServerConnection.SendXmppElement(PresenceElement.Create(resource, PresenceElementType.Subscribed));
            }
        }

        /// <summary>
        /// Send a request to the server to unsubscribe from a resource
        /// </summary>
        /// <param name="resource">The resource from which to unsubscribe</param>
        public void UnsubscribeFromResource(Jid resource)
        {
            if (OkayToSend)
            {
                parentSession.ServerConnection.SendXmppElement(PresenceElement.Create(resource, PresenceElementType.Unsubscribe));
            }
        }
        #endregion

        /// <summary>
        /// Gets or sets the preferred language of received status messages.  If a client has status messages in multiple
        /// languages attached to a presence update, the "primary" message will be that tagged with the preferred language.
        /// </summary>
        public CultureInfo PreferredLanguage
        {
            get { return preferredLanguage; }
            set
            {
                if (value != null)
                {
                    preferredLanguage = value;
                    preferredLanguageCode = preferredLanguage.TwoLetterISOLanguageName;
                }
            }
        }

        #region Internal methods and properties
        /// <summary>
        /// Sends the initial presence announcement to the server
        /// </summary>
        protected internal void SendInitialPresence()
        {
            if (currentPresence == Presence.Unavailable)
            {
                Connection.SendXmppElement(PresenceElement.Create());
                currentPresence = Presence.Available;
            }
            else
            {
                Connection.SendXmppElement(PresenceElement.Create(parentSession.JID, currentPresence));
            }
        }

        /// <summary>
        /// Raises the <see cref="ContactStatusChange"/> event
        /// </summary>
        /// <param name="element">The <see cref="PresenceElement"/> triggering the update</param>
        protected internal void OnContactStatusChange(PresenceElement element)
        {
            Jid contact = new Jid(element.From);
            Presence presence = element.PresenceIndicator;
            string defaultStatusMessage = element.GetStatusMessageByLanguage(preferredLanguageCode);

            if (ContactStatusChange != null)
            {
                ContactStatusChange(this, new ContactStatusChangeEventArgs(contact, presence, defaultStatusMessage));
            }
        }

        /// <summary>
        /// Raises the <see cref="SubscriptionRequested"/> event
        /// </summary>
        /// <param name="resource">The <see cref="Jid"/> of the resource requesting subscription</param>
        protected internal void OnSubscriptionRequested(Jid resource)
        {
            if (SubscriptionRequested != null)
            {
                SubscriptionRequested(this, new SubscriptionRequestEventArgs(resource));
            }
        }

        /// <summary>
        /// Sends a &lt;presence&gt; element using the currently set presence information and status message
        /// </summary>
        private void SetCurrentStatus()
        {
            if (OkayToSend)
            {
                parentSession.ServerConnection.SendXmppElement(
                    PresenceElement.Create(parentSession.JID, currentPresence, currentStatusMessage, currentStatusMessageLanguage));
            }
        }

        /// <summary>
        /// Gets the session's XmppConnection
        /// </summary>
        private XmppConnection Connection
        {
            get { return parentSession.ServerConnection; }
        }

        /// <summary>
        /// Gets a value indicating whether the session is able to send packets
        /// </summary>
        private bool OkayToSend
        {
            get { return parentSession.ServerConnection != null && parentSession.ServerConnection.State == ConnectionState.Connected; }
        }
        #endregion
    }
}
